#include <stream.hpp>


#include <cubefs/core/api/models/manager.hpp>
//#include <cubefs/core/tools/types/dispatcher.hpp>

using namespace cubefs::tools;
using namespace cubefs::api;

#define CUBEFS_SOURCE "stream.cpp:" CUBEFS_AT

using namespace cubefs::api;

jmethodID inReadSingleMID   = NULL;
jmethodID inReadArrayMID    = NULL;
jmethodID inAvlMID          = NULL;
jmethodID inCloseMID        = NULL;

jmethodID outWriteMID       = NULL;
jmethodID outFlushMID       = NULL;
jmethodID outCloseMID       = NULL;

void InitStreams(JNIEnv *env, jclass inputStream, jclass outputStream) {
    inReadSingleMID = env->GetMethodID(inputStream, "read", "()I");
    if (inReadSingleMID == NULL) return;

    inReadArrayMID = env->GetMethodID(inputStream, "read", "([BII)I");
    if (inReadArrayMID == NULL) return;

    inAvlMID = env->GetMethodID(inputStream, "available", "()I");
    if (inAvlMID == NULL) return;

    inCloseMID = env->GetMethodID(inputStream, "close", "()V");
    if (inCloseMID == NULL) return;


    outWriteMID = env->GetMethodID(outputStream, "write", "([BII)V");
    if (outWriteMID == NULL) return;

    outCloseMID = env->GetMethodID(outputStream, "close", "()V");
    if (outCloseMID == NULL) return;

    outFlushMID = env->GetMethodID(outputStream, "flush", "()V");
    if (outFlushMID == NULL) return;
}

void CleanStreams(JNIEnv *env) {
    inReadSingleMID   = NULL;
    inReadArrayMID    = NULL;
    inAvlMID          = NULL;
    inCloseMID        = NULL;

    outWriteMID       = NULL;
    outFlushMID       = NULL;
    outCloseMID       = NULL;
}


class InputStreamAdaptor: public InputStreamHandlerImpl {

    static const Int BUFFER_SIZE = 4096;

    JNIEnv *env;
    jclass clazz_;

    jbyteArray buffer_;

public:
    InputStreamAdaptor(JNIEnv *env0, jclass clazz): env(env0) {
        clazz_ = (jclass)env->NewGlobalRef(clazz);
        CUBEFS_EXC_ASSERT(clazz_!=NULL);

        jbyteArray arr = env->NewByteArray(BUFFER_SIZE);

        CUBEFS_EXC_ASSERT(arr!=NULL);

        buffer_ = (jbyteArray)env->NewGlobalRef(arr);
        CUBEFS_EXC_ASSERT(buffer_!=NULL);
    }

    virtual ~InputStreamAdaptor() throw () {
        try {
            env->DeleteGlobalRef(clazz_);
            env->DeleteGlobalRef(buffer_);
        }
        catch (...) {}
    }

    virtual int buffer_size() {
        return BUFFER_SIZE;
    }

    jobject object() {
        if (GetPeer() == 0) {
            throw NullPointerException(CUBEFS_SOURCE, "No Java peer for input stream handler");
        }

        return (jobject)GetPeer();
    }

    virtual Int available() {
        jobject obj = object();
        
        int result = env->CallIntMethod(obj, inAvlMID);

        CUBEFS_EXC_TEST0()

        return result;
    }

    virtual void close() {
        jobject obj = object();
        env->CallVoidMethod(obj, inCloseMID);
    }

    virtual Int read(void* mem, Int offset, Int length) {
        jobject obj = object();

        if (mem == NULL) {
            throw NullPointerException(CUBEFS_SOURCE, "memory buffer must not be null");
        }

        if (offset > BUFFER_SIZE) {
            throw BoundsException(CUBEFS_SOURCE, "offset is out of bounds", offset, 0, BUFFER_SIZE);
        }

        if (offset + length > BUFFER_SIZE) {
            throw BoundsException(CUBEFS_SOURCE, "offset+lenght is out of bounds", offset+length, 0, BUFFER_SIZE);
        }
        
        jint readed = env->CallIntMethod(obj, inReadArrayMID, buffer_, offset, length);

        CUBEFS_EXC_TEST0()

        if (readed > 0) env->GetByteArrayRegion(buffer_, offset, readed, (jbyte*)mem);

        CUBEFS_EXC_TEST0()

        return readed;
    }
};


JNIEXPORT jlong JNICALL MODELS_PKG(InputStreamAdaptor, createNew) (JNIEnv *env, jclass clazz)
{
    CUBEFS_EXC_BEGIN

    return Ptr2Handle(new InputStreamAdaptor(env, clazz));

    CUBEFS_EXC_HANDLE
    return 0;
}







class OutputStreamAdaptor: public OutputStreamHandlerImpl {

    static const Int BUFFER_SIZE = 4096;

    JNIEnv *env;
    jclass clazz_;

    jbyteArray buffer_;

public:
    OutputStreamAdaptor(JNIEnv *env0, jclass clazz): env(env0) {
        clazz_ = (jclass)env->NewGlobalRef(clazz);
        CUBEFS_EXC_ASSERT(clazz_!=NULL);

        jbyteArray arr = env->NewByteArray(BUFFER_SIZE);

        CUBEFS_EXC_ASSERT(arr!=NULL);

        buffer_ = (jbyteArray)env->NewGlobalRef(arr);
        CUBEFS_EXC_ASSERT(buffer_!=NULL);
    }

    virtual ~OutputStreamAdaptor() throw () {
        try {
            env->DeleteGlobalRef(clazz_);
            env->DeleteGlobalRef(buffer_);
        }
        catch (...) {}
    }

    virtual int buffer_size() {
        return BUFFER_SIZE;
    }

    jobject object() {
        if (GetPeer() == 0) {
            throw NullPointerException(CUBEFS_SOURCE, "No Java peer for input stream handler");
        }

        return (jobject)GetPeer();
    }

    virtual void flush() {
        jobject obj = object();
        env->CallVoidMethod(obj, outFlushMID);
        CUBEFS_EXC_TEST0()
    }

    virtual void close() {
        jobject obj = object();
        env->CallVoidMethod(obj, outCloseMID);
        CUBEFS_EXC_TEST0()
    }

    virtual void write(const void* mem, Int offset, Int length) {
        jobject obj = object();

        if (mem == NULL) {
            throw new NullPointerException(CUBEFS_SOURCE, "memory buffer must not be null");
        }

        if (offset > BUFFER_SIZE) {
            throw new BoundsException(CUBEFS_SOURCE, "offset is out of bounds", offset, 0, BUFFER_SIZE);
        }

        if (offset + length > BUFFER_SIZE) {
            throw new BoundsException(CUBEFS_SOURCE, "offset+lenght is out of bounds", offset+length, 0, BUFFER_SIZE);
        }

        env->SetByteArrayRegion(buffer_, offset, length, (jbyte*)mem);

        CUBEFS_EXC_TEST0()

        env->CallVoidMethod(obj, outWriteMID, buffer_, offset, length);

        CUBEFS_EXC_TEST0()
    }
};


JNIEXPORT jlong JNICALL MODELS_PKG(OutputStreamAdaptor, createNew) (JNIEnv *env, jclass clazz)
{
    CUBEFS_EXC_BEGIN

    OutputStreamAdaptor* obj = new OutputStreamAdaptor(env, clazz);
    return Ptr2Handle(obj);

    CUBEFS_EXC_HANDLE
    return 0;
}


#undef CUBEFS_SOURCE
